﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

#endregion

namespace ScrumTable.Common.Collections
{
    /// <summary>
    /// This class holds multiple indexes for the same value. Every indexed
    /// value entry is bound and optimized by the associated key type. In order
    /// to retrieve the keys of a value, you have to provide a method pointer
    /// as first constructor argument.
    /// </summary>
    public class MultiIndex<TValue> : IEnumerable<TValue>
        where TValue : IMultiIndexValue
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private readonly IDictionary<string, IDictionary<object, TValue>> _inner
            = new Dictionary<string, IDictionary<object, TValue>>();

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new MultiIndex instance with the specified data.
        /// </summary>
        public MultiIndex()
        {
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public IEnumerator<TValue> GetEnumerator()
        {
            var valuePair = _inner.FirstOrDefault();

            if (!Equals(valuePair, default(KeyValuePair<string, IDictionary<object, TValue>>)))
            {
                return _inner.First().Value.Values.GetEnumerator();
            }
            return new Enumerable<TValue>().GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Gets the all keys inside the given index.
        /// </summary>
        /// <param name="field">Specifies the index field name.</param>
        /// <returns>Returns the retrieved keys.</returns>
        public ICollection<object> GetKeys(string field)
        {
            return GetIndexDictionary(field).Keys;
        }

        /// <summary>
        /// Gets the value according to the specified key.
        /// </summary>
        /// <param name="field">Specifies the index field name.</param>
        /// <param name="key">Specifies the key of the element to retrieve.</param>
        /// <returns>Returns the retrieved element.</returns>
        public TValue Get(string field, object key)
        {
            PreCondition.AssertNotNull(key, "key");

            return GetIndexDictionary(field)[key];
        }

        /// <summary>
        /// Sets the given value into the index. If there's already an element with the same
        /// key registered, it will be removed from the index.
        /// </summary>
        /// <param name="toAdd">Specifies the element to add to the dictionary.</param>
        public void Set(TValue toAdd)
        {
            PreCondition.AssertNotNull(toAdd, "toAdd");
            
            foreach (var key in toAdd.IndexFieldValues)
            {
                var typedIndex = GetIndexDictionary(key.Left);

                if (typedIndex.ContainsKey(key))
                {
                    Remove(typedIndex[key]);
                }
                GetIndexDictionary(key.Left)[key.Right] = toAdd;
            }
        }

        /// <summary>
        /// Adds the given value to the index. If there's already an element with the same
        /// key registered, an exception will be thrown.
        /// </summary>
        /// <param name="toAdd">Specifies the element to add to the dictionary.</param>
        public void Add(TValue toAdd)
        {
            PreCondition.AssertNotNull(toAdd, "toAdd");

            IEnumerable<Pair<string, object>> multiIndex = toAdd.IndexFieldValues;

            // check if the procedure can be completed
            foreach (var key in multiIndex)
            {
                if (GetIndexDictionary(key.Left).ContainsKey(key.Right))
                {
                    throw new InvalidOperationException();
                }
            }

            // perform add-action
            foreach (var key in multiIndex)
            {
                GetIndexDictionary(key.Left).Add(key.Right, toAdd);
            }
        }

        /// <summary>
        /// Removes the value from the index.
        /// </summary>
        /// <param name="toRemove">Specifies the value to remove.</param>
        public void Remove(TValue toRemove)
        {
            if (Equals(toRemove, null)) { return; }

            foreach (var key in toRemove.IndexFieldValues)
            {
                GetIndexDictionary(key.Left).Remove(key.Right);
            }
        }

        /// <summary>
        /// Tries to retrieve the value with the from the index.
        /// </summary>
        /// <param name="field">Specifies the index field name.</param>
        /// <param name="key">Specifies the key of the value to retrieve.</param>
        /// <param name="retrievedValue">Specifies the reference of the value to retrieve.</param>
        /// <returns>Returns true if the value with the given key could be found, otherwise false.</returns>
        public bool TryGet(string field, object key, out TValue retrievedValue)
        {
            retrievedValue = default(TValue);

            if (ContainsKey(field, key))
            {
                retrievedValue = Get(field, key);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Returns true if the current index contains a value with the given key.
        /// </summary>
        /// <param name="field">Specifies the index field name.</param>
        /// <param name="key">Specifies the key of the value to check.</param>
        public bool ContainsKey(string field, object key)
        {
            return (key != null
                && HasIndexedDictionary(field)
                && _inner[field].ContainsKey(key));
        }

        private bool HasIndexedDictionary(string field)
        {
            return _inner.ContainsKey(field);
        }

        private IDictionary<object, TValue> GetIndexDictionary(string field)
        {
            PreCondition.AssertNotNull(field, "field");

            if (!_inner.ContainsKey(field))
            {
                _inner[field] = new Dictionary<object, TValue>();
            }
            return _inner[field];
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}